package ca

import (
	"fmt"
	"strings"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/logging"
	sdkCtx "git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/context"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/core"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/iam"
	"github.com/pkg/errors"
)

var logger = logging.NewLogger("ichainsdk/iam")

type CAClientImpl struct {
	orgName         string
	caName          string
	orgIAMID        string
	cryptoSuite     core.CryptoSuite
	identityManager iam.IdentityManager
	userStore       iam.UserStore
	adapter         *caAdapter
	registrar       iam.EnrollCredential
}

// NewCAClient 创建一个ca客户端
// @param orgName 组织名
// @param ctx sdk上下文
// @param caId
// @return *CAClientImpl
// @return error
func NewCAClient(orgName string, ctx sdkCtx.Client, caId string) (*CAClientImpl, error) {

	if orgName == "" {
		orgName = ctx.IdentityConfig().Client().Organization
	}

	if orgName == "" {
		return nil, errors.New("organization is missing")
	}

	netConfig := ctx.EndpointConfig().NetworkConfig()
	orgConfig, ok := netConfig.Organizations[strings.ToLower(orgName)]
	if !ok {
		return nil, errors.New("org config retrieval failed")
	}
	if len(orgConfig.CertificateAuthorities) == 0 {
		return nil, errors.New("no CAs configured")
	}

	if caId == "" {
		caId = orgConfig.CertificateAuthorities[0]
	}

	// 读取ca配置
	caCfg, ok := ctx.IdentityConfig().CAConfig(caId)
	if !ok {
		return nil, errors.Errorf("error initializing CA [%s]", caId)
	}
	// 适配器模式
	adapter, err := newCAAdapter(caId, ctx.CryptoSuite(), ctx.IdentityConfig())
	if err != nil {
		return nil, errors.Wrapf(err, "error initializing CA [%s]", caId)
	}

	identityManager, ok := ctx.IdentityManager(orgName)
	if !ok {
		return nil, fmt.Errorf("identity manager not found for organization '%s", orgName)
	}

	mgr := &CAClientImpl{
		orgName:         orgName,
		caName:          caCfg.CAName,
		orgIAMID:        orgConfig.IAMID,
		cryptoSuite:     ctx.CryptoSuite(),
		identityManager: identityManager,
		userStore:       ctx.UserStore(),
		adapter:         adapter,
		registrar:       caCfg.Registrar,
	}
	return mgr, nil
}

func (c *CAClientImpl) Enroll(request *iam.EnrollmentRequest) error {

	if c.adapter == nil {
		return fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}
	if request.Name == "" {
		return errors.New("enrollmentID is required")
	}
	if request.Secret == "" {
		return errors.New("enrollmentSecret is required")
	}
	cert, err := c.adapter.Enroll(request)
	if err != nil {
		return errors.Wrap(err, "enroll failed")
	}
	userData := &iam.UserData{
		IAMID:                 c.orgIAMID,
		ID:                    request.Name,
		EnrollmentCertificate: cert,
	}
	err = c.userStore.Store(userData)
	if err != nil {
		return errors.Wrap(err, "enroll failed")
	}
	return nil
}

// CreateIdentity 创建身份
// @param request
// @return *iam.IdentityResponse
// @return error
func (c *CAClientImpl) CreateIdentity(request *iam.IdentityRequest) (*iam.IdentityResponse, error) {

	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}

	if request == nil {
		return nil, errors.New("must provide identity request")
	}

	if request.ID == "" {
		return nil, errors.New("ID is required")
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return nil, err
	}

	return c.adapter.CreateIdentity(registrar.PrivateKey(), registrar.EnrollmentCertificate(), request)
}

func (c *CAClientImpl) ModifyIdentity(request *iam.IdentityRequest) (*iam.IdentityResponse, error) {

	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}

	if request == nil {
		return nil, errors.New("must provide identity request")
	}

	if request.ID == "" {
		return nil, errors.New("ID is required")
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return nil, err
	}

	return c.adapter.ModifyIdentity(registrar.PrivateKey(), registrar.EnrollmentCertificate(), request)
}

func (c *CAClientImpl) RemoveIdentity(request *iam.RemoveIdentityRequest) (*iam.IdentityResponse, error) {

	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}

	if request == nil {
		return nil, errors.New("must provide remove identity request")
	}

	if request.ID == "" {
		return nil, errors.New("ID is required")
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return nil, err
	}

	return c.adapter.RemoveIdentity(registrar.PrivateKey(), registrar.EnrollmentCertificate(), request)

}

func (c *CAClientImpl) GetIdentity(id, caname string) (*iam.IdentityResponse, error) {

	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}

	if id == "" {
		return nil, errors.New("id is required")
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return nil, err
	}

	return c.adapter.GetIdentity(registrar.PrivateKey(), registrar.EnrollmentCertificate(), id, caname)
}

func (c *CAClientImpl) GetAllIdentities(caname string) ([]*iam.IdentityResponse, error) {

	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return nil, err
	}

	return c.adapter.GetAllIdentities(registrar.PrivateKey(), registrar.EnrollmentCertificate(), caname)
}

func (c *CAClientImpl) Reenroll(request *iam.ReenrollmentRequest) error {

	if c.adapter == nil {
		return fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}
	if request.Name == "" {
		logger.Info("invalid re-enroll request, missing enrollmentID")
		return errors.New("user name missing")
	}

	user, err := c.identityManager.GetSigningIdentity(request.Name)
	if err != nil {
		return errors.Wrapf(err, "failed to retrieve user: %s", request.Name)
	}

	cert, err := c.adapter.Reenroll(user.PrivateKey(), user.EnrollmentCertificate(), request)
	if err != nil {
		return errors.Wrap(err, "reenroll failed")
	}
	userData := &iam.UserData{
		IAMID:                 c.orgIAMID,
		ID:                    user.Identifier().ID,
		EnrollmentCertificate: cert,
	}
	err = c.userStore.Store(userData)
	if err != nil {
		return errors.Wrap(err, "reenroll failed")
	}

	return nil
}

func (c *CAClientImpl) Register(request *iam.RegistrationRequest) (string, error) {
	if c.adapter == nil {
		return "", fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}
	if c.registrar.EnrollID == "" {
		return "", ErrCARegistrarNotFound
	}
	// Validate registration request
	if request == nil {
		return "", errors.New("registration request is required")
	}
	if request.Name == "" {
		return "", errors.New("request.Name is required")
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return "", err
	}

	secret, err := c.adapter.Register(registrar.PrivateKey(), registrar.EnrollmentCertificate(), request)
	if err != nil {
		return "", errors.Wrap(err, "failed to register user")
	}

	return secret, nil
}

func (c *CAClientImpl) Revoke(request *iam.RevocationRequest) (*iam.RevocationResponse, error) {
	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}
	if c.registrar.EnrollID == "" {
		return nil, ErrCARegistrarNotFound
	}
	// Validate revocation request
	if request == nil {
		return nil, errors.New("revocation request is required")
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return nil, err
	}

	resp, err := c.adapter.Revoke(registrar.PrivateKey(), registrar.EnrollmentCertificate(), request)
	if err != nil {
		return nil, errors.Wrap(err, "failed to revoke")
	}
	return resp, nil
}

func (c *CAClientImpl) GetCAInfo() (*iam.GetCAInfoResponse, error) {
	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}

	return c.adapter.GetCAInfo(c.caName)
}

func (c *CAClientImpl) GetAffiliation(affiliation, caname string) (*iam.AffiliationResponse, error) {
	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}

	// Check required parameters (affiliation)
	if affiliation == "" {
		return nil, errors.New("affiliation is required")
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return nil, err
	}

	return c.adapter.GetAffiliation(registrar.PrivateKey(), registrar.EnrollmentCertificate(), affiliation, caname)
}

func (c *CAClientImpl) GetAllAffiliations(caname string) (*iam.AffiliationResponse, error) {
	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization %s", c.orgName)
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return nil, err
	}

	return c.adapter.GetAllAffiliations(registrar.PrivateKey(), registrar.EnrollmentCertificate(), caname)
}

func (c *CAClientImpl) AddAffiliation(request *iam.AffiliationRequest) (*iam.AffiliationResponse, error) {
	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}

	if request == nil {
		return nil, errors.New("must provide affiliation request")
	}

	// Check required parameters (Name)
	if request.Name == "" {
		return nil, errors.New("Name is required")
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return nil, err
	}

	return c.adapter.AddAffiliation(registrar.PrivateKey(), registrar.EnrollmentCertificate(), request)
}

func (c *CAClientImpl) ModifyAffiliation(request *iam.ModifyAffiliationRequest) (*iam.AffiliationResponse, error) {
	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}

	if request == nil {
		return nil, errors.New("must provide affiliation request")
	}

	// Check required parameters (Name and NewName)
	if request.Name == "" || request.NewName == "" {
		return nil, errors.New("Name and NewName are required")
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return nil, err
	}

	return c.adapter.ModifyAffiliation(registrar.PrivateKey(), registrar.EnrollmentCertificate(), request)
}

func (c *CAClientImpl) RemoveAffiliation(request *iam.AffiliationRequest) (*iam.AffiliationResponse, error) {
	if c.adapter == nil {
		return nil, fmt.Errorf("no CAs configured for organization: %s", c.orgName)
	}

	if request == nil {
		return nil, errors.New("must provide remove affiliation request")
	}

	// Check required parameters (Name)
	if request.Name == "" {
		return nil, errors.New("Name is required")
	}

	registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret)
	if err != nil {
		return nil, err
	}

	return c.adapter.RemoveAffiliation(registrar.PrivateKey(), registrar.EnrollmentCertificate(), request)
}

func (c *CAClientImpl) getRegistrar(enrollID string, enrollSecret string) (iam.SigningIdentity, error) {

	if enrollID == "" {
		return nil, ErrCARegistrarNotFound
	}

	registrar, err := c.identityManager.GetSigningIdentity(enrollID)
	if err != nil {
		if err != iam.ErrUserNotFound {
			return nil, err
		}
		if enrollSecret == "" {
			return nil, ErrCARegistrarNotFound
		}

		// Attempt to enroll the registrar
		err = c.Enroll(&iam.EnrollmentRequest{Name: enrollID, Secret: enrollSecret})
		if err != nil {
			return nil, err
		}
		registrar, err = c.identityManager.GetSigningIdentity(enrollID)
		if err != nil {
			return nil, err
		}
	}
	return registrar, nil
}
